//    Copyright (C) Kherty.  All rights reserved.
#region

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using OpenLS.Drawing;
using OpenLS.Drawing.UI;
using TextParagraphProperties=System.Windows.Media.TextFormatting.TextParagraphProperties;

#endregion

namespace OpenLS.Spreadsheet.UI.Internals
{
    internal abstract partial class CellTextBox : Control, IScrollInfo
    {
        // Using a DependencyProperty as the backing store for ColorScheme.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ColorSchemeProperty =
            DependencyProperty.Register("ColorScheme", typeof (ColorScheme), typeof (CellTextBox),
                                        new UIPropertyMetadata(null,
                                                               onColorSchemeChanged));

        // Using a DependencyProperty as the backing store for EditedCell.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EditedCellProperty =
            DependencyProperty.Register("EditedCell", typeof (EditedCell), typeof (CellTextBox),
                                        new UIPropertyMetadata(null, onEditedCellChanged));


        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register("Source", typeof (CellTextSource), typeof (CellTextBox),
                                        new FrameworkPropertyMetadata(null,
                                                                      FrameworkPropertyMetadataOptions.AffectsRender |
                                                                      FrameworkPropertyMetadataOptions.AffectsMeasure,
                                                                      onSourceChanged));

        public static readonly DependencyProperty TextAlignmentProperty =
            DependencyProperty.Register("TextAlignment", typeof (TextAlignment), typeof (CellTextBox),
                                        new FrameworkPropertyMetadata(TextAlignment.Left,
                                                                      FrameworkPropertyMetadataOptions.AffectsRender));

        public static readonly DependencyProperty TextWrappingProperty =
            DependencyProperty.Register("TextWrapping", typeof (TextWrapping), typeof (CellTextBox),
                                        new FrameworkPropertyMetadata(TextWrapping.Wrap,
                                                                      FrameworkPropertyMetadataOptions.AffectsMeasure));
        public CellTextBox()
        {
            FocusElement.SetFocusRedirect(this, this);
#if DEBUG
            Unloaded += new RoutedEventHandler(CellTextBox_Unloaded);
#endif
        }

#if DEBUG
        void CellTextBox_Unloaded(object sender, RoutedEventArgs e)
        {
            Debug.WriteLine("unloading cell textbox");
        }
#endif
        static CellTextBox()
        {
            FocusVisualStyleProperty.OverrideMetadata(typeof (CellTextBox), new FrameworkPropertyMetadata(new Style(typeof (IFrameworkInputElement))));
            FocusableProperty.OverrideMetadata(typeof(CellTextBox), new FrameworkPropertyMetadata(true));
            CellTextEditorSelection.RegisterClassHandlers(typeof(CellTextBox));
            CellTextEditorClipboard.RegisterClassHandlers(typeof(CellTextBox));
            CellTextEditorTyping.RegisterClassHandlers(typeof(CellTextBox));
            CellTextEditorMouse.RegisterClassHandlers(typeof (CellTextBox));
            EventManager.RegisterClassHandler(typeof(CellTextBox), LostFocusEvent, new RoutedEventHandler(sOnChagedFocus));
            EventManager.RegisterClassHandler(typeof(CellTextBox), GotFocusEvent, new RoutedEventHandler(sOnChagedFocus));
            CursorProperty.OverrideMetadata(typeof(CellTextBox), new FrameworkPropertyMetadata(Cursors.IBeam));
        }

        private static void sOnChagedFocus(object sender, RoutedEventArgs e)
        {
            var tb = (CellTextBox) sender;
            tb.InvalidateVisual();
        }



        public ColorScheme ColorScheme
        {
            get { return (ColorScheme) GetValue(ColorSchemeProperty); }
            set { SetValue(ColorSchemeProperty, value); }
        }

        public EditedCell EditedCell
        {
            get { return (EditedCell) GetValue(EditedCellProperty); }
            set { SetValue(EditedCellProperty, value); }
        }

        public CellTextSource Source
        {
            get { return (CellTextSource) GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        public TextWrapping TextWrapping
        {
            get { return (TextWrapping) GetValue(TextWrappingProperty); }
            set { SetValue(TextWrappingProperty, value); }
        }

        public TextAlignment TextAlignment
        {
            get { return (TextAlignment) GetValue(TextAlignmentProperty); }
            set { SetValue(TextAlignmentProperty, value); }
        }

        protected override Size MeasureOverride(Size constraint)
        {
            if (Source == null)
                return new Size(0, 0);
            double height = 0;
            double width = 0;
            int lineCount = 0;
            int visibleLineCount = 0;
            foreach (var line in getLines(constraint.Width))
            {
                if (height + line.TextHeight <= constraint.Height)
                    visibleLineCount ++;
                height += line.TextHeight;
                width = Math.Max(width, line.Width);
                lineCount++;
            }
            lineCount = Math.Max(1, lineCount);
            visibleLineCount = Math.Max(1, visibleLineCount);
            _scrollData.SetSizes(new Size(100, lineCount), new Size(100, visibleLineCount));
            return new Size(Math.Ceiling(width) , height);
        }

        private static void onColorSchemeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tb = (CellTextBox) d;
            if (tb.EditedCell != null)
                tb.Source = new FormulaTextSource(tb.EditedCell, (ColorScheme) e.NewValue);
        }

        private static void onEditedCellChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tb = (CellTextBox) d;
            if (e.NewValue == null) tb.Source = null;
            else
                tb.Source = tb.CreateTextSource((EditedCell)e.NewValue, tb.ColorScheme, tb.FontFamily, tb.FontSize);
               /* if (tb.InFormulaBar)
                    tb.Source = new InFormulaTextSource((EditedCell)e.NewValue, tb.ColorScheme, tb.FontFamily,tb.FontSize);
                    else
            tb.Source = new FormulaTextSource((EditedCell) e.NewValue, tb.ColorScheme);*/
        }

        protected abstract CellTextSource CreateTextSource(EditedCell newValue, ColorScheme colorScheme,
                                                         FontFamily fontFamily, double fontSize);

        protected override void OnRender(DrawingContext drawingContext)
        {
            Brush brush = Background ?? Brushes.White;
            Contract.Assert(brush != null);
            {
                drawingContext.DrawRectangle(brush, null, new Rect(new Point(0, 0), new Size(ActualWidth, ActualHeight)));
            }
            if (Source == null)
                return;
            if ((this is FormulaCellTextBox) && EditedCell != null && EditedCell.Cell.IsFormulaHidden && this.EditedCell.Cell.Worksheet.IsProtected (p => true))
            {
                return;
            }
#if DEBUG
         //   drawingContext.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 10, 10));
#endif
            var index = 0;
            double y = 0;
            const double x = 1;
            bool isFocused = IsFocused;
            int minIndex = Source.MinIndex;
            int maxIndex = Source.MaxIndex;
            int lineIndex = -1;
            int minLineIndex = _scrollData.OffsetY;
            foreach (var line in  getLines(ActualWidth))
            {

                if (Source.GetTextRun(index) is TextEndOfParagraph)
                    break;
                lineIndex++;
                int maxLineIndex = index + line.Length;
                if (lineIndex < minLineIndex)
                {
                    index += line.Length;
                    continue;
                }
                if (minIndex != maxIndex)
                {
                    if (minIndex <= maxLineIndex && maxIndex >= index)
                    {
                        var position1 = line.GetDistanceFromCharacterHit(new CharacterHit(Math.Max(index, minIndex), 0));
                        var position2 =
                            line.GetDistanceFromCharacterHit(new CharacterHit(Math.Min(maxLineIndex, maxIndex), 0));
                        drawingContext.DrawRectangle(Foreground, null,
                                                     new Rect(position1 + x, y, position2 - position1, line.TextHeight));
                    }
                }
                line.Draw(drawingContext, new Point(x, y), InvertAxes.None);
                if (minIndex == maxIndex)
                {
                    if (isFocused && Source.SelectionIndex >= index && Source.SelectionIndex <= maxLineIndex)
                    {
                        var position = line.GetDistanceFromCharacterHit(new CharacterHit(Source.SelectionIndex, 0));
                        drawingContext.DrawRectangle(Foreground, null, new Rect(position + x, y, 1, line.TextHeight));
                    }
                }
                y += line.TextHeight;
                index += line.Length;
            }
        }

        private IEnumerable<TextLine> getLines(double width)
        {
            TextParagraphProperties textParagraphProperties = createTextParagraphProperties();

            var formatter = TextFormatter.Create();
            var index = 0;
            while (true)
            {
                if (Source.GetTextRun(index) is TextEndOfParagraph)
                    break;
                var line = formatter.FormatLine(Source, index, width, textParagraphProperties, null);
                yield return line;
                index += line.Length;
            }
        }


        private static void onSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tb = (CellTextBox) d;

            tb.onSourceChanged(e);
        }




       
        private CellTextParagraphProperties createTextParagraphProperties()
        {

            return new CellTextParagraphProperties(createRunProperties(), FlowDirection, TextAlignment, TextWrapping);
        }

        protected virtual CellTextRunPropertiesBase createRunProperties()
        {
           //\\ InFormulaBar = true;
            if (true)
                return new FormulaBarCellTextRunProperties(EditedCell, this.FontFamily, this.FontSize);
            return new CellTextRunProperties(EditedCell, DrawingElement.GetColorScheme(this));
                // createTypeFace(), FontSize, null, Foreground);
        }


        private void onSourceChanged(DependencyPropertyChangedEventArgs e)
        {
            var oldSource = (CellTextSource) e.OldValue;
            var newSource = (CellTextSource) e.NewValue;
            if (oldSource != null)
            {
                oldSource.PropertyChanged -= onSourceModified;
            }
            if (newSource != null)
            {
                newSource.PropertyChanged += onSourceModified;
            }
        }

        private void onSourceModified(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "Text":
                    InvalidateVisual();
                    break;
                    default: InvalidateVisual();
                    break;
            }
            InvalidateMeasure();
        }

        public int GetLineStart(int ii)
        {
            var index = 0;
            foreach (var line in getLines(ActualWidth))
            {
                if (line.Length + index > ii)
                    return index;
                index += line.Length;
            }
            throw new InvalidOperationException();
        }

        public int GetLineEnd(int ii)
        {
            var index = 0;
            foreach (var line in getLines(ActualWidth))
            {
                if (line.Length + index > ii)
                    return index + line.Length;
                index += line.Length;
            }
            throw new InvalidOperationException();
        }

        public void MoveAt(Point pt)
        {
            var index = CharacterIndexAt(pt);
            if (index != null)
                Source.Move(index.Value);
        }
        public void SelectAt(Point pt)
        {
            var index = CharacterIndexAt(pt);
            if (index != null)
                Source.Select(index.Value);
        }
        int? CharacterIndexAt (Point point)
        {
            int index = 0;
            double y = 0;
            foreach (var line in getLines(ActualWidth))
            {
                if (line.TextHeight + y >= point.Y)
                {
                    var hit = line.GetCharacterHitFromDistance(point.X);
                    return hit.FirstCharacterIndex + hit.TrailingLength ;
                }
                y += line.TextHeight;
                index += line.Length;
            }
            return null;
            
        }
    }
}